﻿using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;

namespace ProductCollector
{
    public class Product
    {
        public string ProductName { get; set; }
        public string OriginalUrl { get; set; }
        public string Description { get; set; }
        public string ThumbImg { get; set; }
        public int Stock { get; set; }
        public float Price { get; set; }
        public string Manufacturer { get; set; }
        public string FormattedPrice { get; set; }
        public ObjectKey Key
        {
            get { return ObjectKey.FromParams(ProductName, OriginalUrl, ThumbImg, Price); }
        }
        public string ProductId
        {
            get { return Key.Serialize(); }
        }

        public static Product Deserialize(string str)
        {
            ObjectKey key = ObjectKey.FromSerialized(str);
            return new Product
                {
                    ProductName = (string)key.Keys[0],
                    OriginalUrl = (string)key.Keys[1],
                    ThumbImg = (string)key.Keys[2],
                    Price = (float)key.Keys[3],
                };
        }

    }
    public class ObjectKey
    {
        #region Constants

        public const string BASE32 = "QAZ2WSX3EDC4RFV5TGB6YHN7UJM8K9LP";

        #endregion Constants

        #region IFields

        public object[] Keys { get; internal set; }

        public string Context { get; set; }

        #endregion IFields

        #region IConstructors

        protected ObjectKey(params object[] keys)
        {
            Keys = keys;
        }

        protected ObjectKey(string key)
        {
            using (MemoryStream ms = new MemoryStream(FromBase32String(key)))
            {
                //using (GZipStream ds = new GZipStream(ms, CompressionMode.Decompress, true)) {
                BinaryFormatter bf = new BinaryFormatter();
                Keys = (object[])bf.Deserialize(ms);
                //}
            }
        }

        #endregion IConstructors

        #region IMethods

        public string Serialize()
        {
            string ret = null;
            using (MemoryStream ms = new MemoryStream())
            {
                //using (GZipStream ds = new GZipStream(ms, CompressionMode.Compress, true)) {
                BinaryFormatter bf = new BinaryFormatter();
                bf.Serialize(ms, Keys);
                //}
                ms.Position = 0L;
                ret = ToBase32String(ms.ToArray());
            }
            return ret;

        }

        public override bool Equals(object obj)
        {
            if (obj == null || (obj as ObjectKey) == null)
            {
                return false;
            }
            ObjectKey value = obj as ObjectKey;

            if ((this.Keys == null || value.Keys == null)
                || (this.Keys.Length != value.Keys.Length))
            {
                return false;
            }

            for (int i = 0; i < Keys.Length; i++)
            {
                if (!Keys[i].Equals(value.Keys[i]))
                {
                    return false;
                }
            }
            return true;
        }

        public override int GetHashCode()
        {
            if (Keys == null || Keys.Length == 0)
                return base.GetHashCode();
            int value = Keys[0].GetHashCode();
            for (int i = 1; i < Keys.Length; i++)
            {
                value ^= Keys[i].GetHashCode();
            }
            return value;
        }

        #endregion IMethods

        #region SMethods

        public static ObjectKey FromParams(params object[] args)
        {
            return new ObjectKey(args);
        }

        public static ObjectKey FromSerialized(string ser)
        {
            return new ObjectKey(ser);
        }

        /// <summary>
        /// Converts an array of bytes to a Base32-k string.
        /// </summary>
        private static string ToBase32String(byte[] bytes)
        {
            StringBuilder sb = new StringBuilder(); // holds the base32 chars
            byte index;
            int hi = 5;
            int currentByte = 0;

            while (currentByte < bytes.Length)
            {
                // do we need to use the next byte?
                if (hi > 8)
                {
                    // get the last piece from the current byte, shift it to the right
                    // and increment the byte counter
                    index = (byte)(bytes[currentByte++] >> (hi - 5));
                    if (currentByte != bytes.Length)
                    {
                        // if we are not at the end, get the first piece from
                        // the next byte, clear it and shift it to the left
                        index = (byte)(((byte)(bytes[currentByte] << (16 - hi)) >> 3) | index);
                    }

                    hi -= 3;
                }
                else if (hi == 8)
                {
                    index = (byte)(bytes[currentByte++] >> 3);
                    hi -= 3;
                }
                else
                {

                    // simply get the stuff from the current byte
                    index = (byte)((byte)(bytes[currentByte] << (8 - hi)) >> 3);
                    hi += 5;
                }

                sb.Append(BASE32[index]);
            }

            return sb.ToString();
        }


        /// <summary>
        /// Converts a Base32-k string into an array of bytes.
        /// </summary>
        /// <exception cref="System.ArgumentException">
        /// Input string <paramref name="s">s</paramref> contains invalid Base32-k characters.
        /// </exception>
        private static byte[] FromBase32String(string str)
        {
            int numBytes = str.Length * 5 / 8;
            byte[] bytes = new Byte[numBytes];

            // all UPPERCASE chars
            str = str.ToUpper();

            int bit_buffer;
            int currentCharIndex;
            int bits_in_buffer;

            if (str.Length < 3)
            {
                bytes[0] = (byte)(BASE32.IndexOf(str[0]) | BASE32.IndexOf(str[1]) << 5);
                return bytes;
            }

            bit_buffer = (BASE32.IndexOf(str[0]) | BASE32.IndexOf(str[1]) << 5);
            bits_in_buffer = 10;
            currentCharIndex = 2;
            for (int i = 0; i < bytes.Length; i++)
            {
                bytes[i] = (byte)bit_buffer;
                bit_buffer >>= 8;
                bits_in_buffer -= 8;
                while (bits_in_buffer < 8 && currentCharIndex < str.Length)
                {
                    bit_buffer |= BASE32.IndexOf(str[currentCharIndex++]) << bits_in_buffer;
                    bits_in_buffer += 5;
                }
            }

            return bytes;
        }



        #endregion SMethods
    }
}